package com.peng.scalatest

import java.util.Date

/**
 * 函数
 */
object ScalaTest3_Function {


  //scala的函数返回值可以不给出返回类型，可以根据最后一行代码的结果值推断出返回类型，但一般建议手动给出返回类型进行约束
  def myFun() = {
    123
  }

  //有return  必须给出返回类型
  def myFun2(): String = {
    return "asd" + 3
  }

  //编译型语言，无法预测函数中的参数的类型，需要使用者明确指定

  def myFun3(str: String): Unit = {
    println(str)
  }

  //==============================以上为函数base========================================================================

  //1、编写递归函数时，需要明确返回类型，因为不知道递归到什么时候，无法推测返回类型
  def deepFun(num: Int): Int = {
    if (num == 1) {
      1
    } else {
      num * deepFun(num - 1)
    }

  }


  //2、默认值参数
  def myFun4(a: String = "haha", b: Int = 23): Unit = {
    print(a, b)
  }


  //3、匿名函数
  def myFun5(): Unit = {
    val function: (Int, String) => String = (a, b) => {
      a + b
    } //scala中 函数也是一种值，对于这种值的一个类型签名格式是这样的：(Int, String) => String   包含入参列表 和 返回值类型
    val str = function(1, "hei")
    println(str)
  }

  //4、嵌套函数
  def myFun6(a: String): Unit = {
    def myFun5(): Unit = {
      println(a)
    }

    myFun5()
  }


  //5、偏应用函数
  def myFun7(date: Date, ty: Int, msg: String): Unit = {
    println(date, ty, msg)
  }

  def myFun7_2(): Unit = {
    val level_1 = myFun7(_: Date, 1, _: String) //将原函数进行自定义入口， 使用 _:类型  占位表明仍需传入的参数
    val level_2 = myFun7(new Date(), 2, _: String)
    level_1(new Date(), "hello1")
    level_2("hei")
  }

  //6、可变参数函数
  def myFun8(a: Int*): Unit = {

    //    for (elem <- a) {
    //      println(elem)
    //    }


    //另一种循环方式：foreach，接受一个函数参数
    //    a.foreach((elem:Int)=>{
    //      println(elem)
    //    })

    //在已经声明确定函数前面类型的情况下，传入函数值可以不用再写它的类型，只写参数就可以
    //匿名函数类型签名 如果只有一个入参，入参参数的签名可以不加括号
    //    例如：var paramFun:(Int,String)=>Unit = (a,b)=>{}
    //    a.foreach(item=>{
    //      println(item)
    //    })

    //如果只使用一次这个参数，可以：
    //    a.foreach(println(_))
    //    或者更简化
    a.foreach(println)


  }


  //  7、高阶函数
  def myFun9(): Unit = {
    computer(3, 5, (x, y) => {
      x + y
    })
    computer(3, 5, _ + _) //语法糖写法  当入参的参数只使用一次， 且按顺序出现  则可以这么使用
    //   computer(3,5,asd(_,_))
    computer(3, 5, computeFunctionFactory("+"))
  }

  def computer(a: Int, b: Int, f: (Int, Int) => Int): Unit = {
    println(f(a, b))
  }

  //生产计算函数的工厂
  def computeFunctionFactory(ty: String): (Int, Int) => Int = {
    def plus(a: Int, b: Int): Int = {
      a + b
    }

    if (ty.equals("+")) {
      plus
    } else {
      _ * _ //      等同于  (a,b)=>a*b

    }

  }

  def asd(a: Int, b: Int): Int = {
    123
  }

  //8、柯里化
  def myFun10(a: String*)(b: Int*): Unit = {
    a.foreach(println)
    b.foreach(println)
  }


  //9、scala中，无参方法，调用可以不加括号
  def myFun11(): Unit = {
    println("嘿嘿。。。")
  }

  //10、scala中，无参方法，定义可以不加括号
  def myFun12: Unit = {
    println("nice")
  }


  //11、传值函数和传名函数
  /**
   * Scala的解释器在解析函数参数(function arguments)时有两种方式：
   * 先计算参数表达式的值(reduce the arguments)，再应用到函数内部；
   * 或者是将未计算的参数表达式直接应用到函数内部。
   * 前者叫做传值调用（call-by-value），后者叫做传名调用（call-by-name）。
   */

  def addByName(a: Int, b: => Int) = a + b

  def addByValue(a: Int, b: Int) = a + b

  /**
   *
   * => Unit 与 () =>Unit的区别
   *
   * 简单来说, => Unit是 传名函数, 只传入了一个表达式, 在调用时才会去执行, 使用 code调用
   *
   * () => 是传值函数, 传入的计算后的值, 使用 code() 调用
   */


  /**
   * 在进入函数内部前，传值调用方式就已经将参数表达式的值计算完毕，而传名调用是在函数内部进行参数表达式的值计算的。
   *
   * 这就造成了一种现象，每次使用传名调用时，解释器都会计算一次表达式的值。对于有副作用(side-effect)的参数来说，这无疑造成了两种调用方式结果的不同。
   *
   * 因此, 在实际的使用中:
   *
   * 传值调用在进入函数体之前就对参数表达式进行了计算，这避免了函数内部多次使用参数时重复计算其值，在一定程度上提高了效率。
   *
   * 但是传名调用的一个优势在于，如果参数在函数体内部没有被使用到，那么它就不用计算参数表达式的值了。在这种情况下，传名调用的效率会高一点。
   *
   */

  def delayTime2(t: () => Long): Unit = {
    println("执行...")
    println(t())
    println("jiesu..")
  }

  def delayTime(t: => Long): Unit = {
    println("执行开始")
    //两次调用t代码块，代码块被执行了两次
    println(t)
    println(t)
    println("执行结束")
  }

  def time() = {
    println("获取时间内, 单位为 纳秒")
    System.nanoTime()
  }

  def main(args: Array[String]): Unit = {
    //    println(myFun())
    //    println(myFun2())
    //    println(myFun3("哈塞给"))
    //    println(deepFun(3))
    //    myFun4()
    //    myFun6("789")
    //    myFun7_2()
    //    myFun8(1,2,3,4,5)
    //    myFun9()
    //    _*作为一个整体，告诉编译器你希望将某个参数当作参数序列处理！例如myFun8(1 to 10:_*)就是将1 to 5当作参数序列处理。
    //    myFun8(1 to 10:_*)

    //    myFun10("a", "b", "c")(1 to 10: _*)

    //scala中，无参方法，调用可以不加括号  因此，如下并不能做到方法的引用赋值给一个变量
    //    var fun = myFun11

    //要这么做：  方法名 +空格 + _
    //    var fun = myFun11 _
    //scala中，万物皆对象，没有基本类型，包括数字间的加减乘除都是调用的方法  如下 实际是通过 点 操作符 调用数字的 “+”方法
    //    val num = 3.+(2)


    //    addByValue(3, {
    //      3 + 3
    //    })
    //    addByName(3, {
    //      1 match {
    //        case 1 => 1
    //      }
    //    })

    //    delayTime({
    //      println("2222");
    //      time()
    //    })
    //    delayTime2({
    //      println("333");
    //      time
    //    })

    /**
     * 通过上面delayTime和delayTime2的调用，看似好像在scala中可以直接通过{}在中间写入多行逻辑，只要返回值是 传入函数参数要求的返回值，就可以完美适配
     * 但其实，这种只适用于 参数函数为 f:()=>Int   或者  :=>Int  这样的 无参的函数参数
     *
     * 那假如我想要传入一个有参的函数应该怎么做？
     * 这要引出偏函数的概念，所谓偏函数(也叫部分函数)与完全函数想对应，
     * 普通的方法都是完全函数，即 f(i:Int) = xxx 是将所有Int类型作为参数的，是对整个Int集的映射；
     * 而偏函数则是对部分数据的映射，比如上面{case 2=> "OK" }就仅仅只对2做了映射。偏函数的实现都是通过模式匹配来表达的。
     *
     * scala> val p:PartialFunction[Int,String] = { case 2 => "OK" }
     * 因为偏函数是通过 { case x => y } 这种特殊的方式来描述的，
     * 上面的{case 2=>"OK"}就被当作了一段偏函数字面量，而偏函数背后的类型PartialFunction[A,B]是继承自Function1[A,B]的，所以将这段匿名的偏函数传给方法作为有参函数参数是ok的。
     *
     * 小结：表达式 {case x=>y}会被当作偏函数字面量。
     */


    callTest({
      case 1 => 1
      case _ => 22
    })
    //多变量接收元组返回值(需要保证参数个数与返回值一致,否则编译期报错)
    //如果确实只想得到多个返回中的部分返回,可以使用_进行占位
    val (a, b, _) = mutiReturnDef()
    println(a)
    println(b)

    //多变量接收Array返回值(需要保证参数个数与返回值一致,否则运行期报错)
    //如果确实只想得到多个返回中的部分返回,可以使用_进行占位
    val List(c, d, _) = mutiReturnDefList()
    println(c)
    println(d)



  }

  def callTest[T](x: Int => T): Unit = println(x(2))


  def mutiReturnDef(): (Int, Int, Int) = {
    (1, 2, 3)
  }

  def mutiReturnDefList(): List[Int] = {
    List(1, 2, 3)
  }


}
